package org.swift.mashup.engine.deployment;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMException;
import org.swift.commons.xml.OMUtils;
import org.swift.commons.xml.XmlUtils;
import org.swift.mashup.engine.concurrency.execution.ExecutionOutputMode;
import org.w3c.dom.Node;

@SuppressWarnings("unchecked")
public class OMResultFormatter implements
		MashupResultFormatter<Map<String,OMElement>> {

	public Map<String,OMElement> formatResults(
			Map<String, ? extends Object> mashupResults)
			throws FormatterException {
		Map<String,OMElement> map = new HashMap<String, OMElement>();
		try {
			for (Entry<String, ?> entry : mashupResults.entrySet()) {
				OMElement value = this.formatResult(entry.getKey(), entry.getValue());
				map.put(entry.getKey(), value);
			}
			return map;
		} catch (OMException ome){
			throw new FormatterException(
				"Unable to create AXIOM from the mashup results.", ome);
		} catch (XMLStreamException e) {
			throw new FormatterException(
				"Unable to create AXIOM from the mashup results.", e);
		}
	}
	
	// process individual results to generate an om element
	private OMElement formatResult(String entryKey, Object entryValue)
			throws FormatterException, XMLStreamException{
        ExecutionOutputMode mode = ExecutionOutputMode.parseOutputMode(entryKey);

        if ( mode.isDefault() ) {
            return formatDefaultResult(entryKey, entryValue);
        }

        String memoryItem = ExecutionOutputMode.parseMemoryItem(entryKey);
        OMElement element = OMAbstractFactory.getOMFactory().createOMElement(new QName(mode.getNamespace(),memoryItem));
        element.setText(convertToString(entryValue));
        return element;
    }

    private String convertToString(Object obj) {
        if ( obj == null ) {
            return null;
        }

        if ( obj instanceof Collection ) {
            Collection c = (Collection)obj;

            if ( c.size() == 1 ) {
                return convertToString(c.iterator().next());
            }
        }

        return obj.toString();
    }

    private OMElement formatDefaultResult(String entryKey, Object entryValue)
            throws FormatterException, XMLStreamException{
		if (entryValue instanceof Node) {
			return this.nodeToOMElement((Node) entryValue);
		} else if (entryValue instanceof Collection) {
			Collection col = (Collection)entryValue;
			if(col.size()==1 && entryKey.startsWith("_")){
				Object contents = col.iterator().next();
				return this.formatDefaultResult(entryKey, contents);
			}else{
                String elementName = ExecutionOutputMode.parseMemoryItem(entryKey);
                OMElement value = OMUtils.stringToOMElement("<" + elementName + "/>");
				return this.collectionToXml(value, col);
			}
		} else {
			return this.stringToXml(entryValue.toString(), entryKey);
		}
	}

	private OMElement nodeToOMElement(Node node) throws FormatterException {
		try {
			return OMUtils.stringToOMElement(XmlUtils.toString(node));
		} catch (XMLStreamException e) {
			throw new FormatterException(
				"Failed to convert w3c Node to OMElement [w3c node as text: "
					+ XmlUtils.toString(node) + "]", e);
		}
	}

	// appends the collection values to the om element
	private OMElement collectionToXml(OMElement node, Collection<?> col)
			throws FormatterException, XMLStreamException {
		String nodeName = node.getLocalName();
		boolean isWrappingPrimitives = false;
		for (Object obj : col) {
			if (obj instanceof Node) {
				node.addChild(nodeToOMElement((Node) obj));
			} else if (obj instanceof String) {
				String data = (String) obj;
				if (!attachChildElement(data, node, nodeName)){
					isWrappingPrimitives = true;
				}
			} else if (obj instanceof byte[]) {
				String str = new String((byte[]) obj);
				if (!attachChildElement(str, node, nodeName)){
					isWrappingPrimitives = true;
				}
			} else {
				attachChildElement(obj.toString(),node,nodeName);
			}
		}
		if (isWrappingPrimitives){
			node.getLocalName();
			node.setLocalName(getPlural(node.getLocalName()));
		}
		return node;
	}
	
	// turns string content into xml and appends to the om element
	private OMElement stringToXml(String content, String nodeName) 
			throws FormatterException{
		if (!XmlUtils.isXml(content)){
			content = wrapNonXml(nodeName,content);
		}
		try {
			return OMUtils.streamToOMElement(new ByteArrayInputStream(
				content.getBytes()));
		} catch (XMLStreamException e) {
			throw new FormatterException(
					"Unable to create AXIOM from the mashup results.", e);
		}
	}
	
	protected String wrapNonXml (String nodeName, String xml){
		return String.format("<%s>%s</%s>", nodeName,xml,nodeName);
	}
	
	/**
	 * Returns false if the text passed in was not valid xml.
	 * 
	 * @param xmlOrText
	 * @param toNode
	 * @return
	 */
	protected boolean attachChildElement (String xmlOrText, OMElement toNode, String nodeName){
		boolean isValidXML = true;
		if (!XmlUtils.isXml(xmlOrText)){
			xmlOrText = wrapNonXml(nodeName,xmlOrText);
			isValidXML = false;
		}
		attachChildElement(xmlOrText,toNode);
		return isValidXML;
	}
	
	private void attachChildElement (String xmlContent, OMElement toNode){
		try {
			OMElement child = OMUtils.streamToOMElement(
				new ByteArrayInputStream(xmlContent.getBytes()));
			toNode.addChild(child);
		} catch (XMLStreamException xse){
			OMElement el = OMAbstractFactory.getOMFactory().
				createOMElement("XMLConversionFailed",toNode.getNamespace());
			el.setText("Unable to convert this value to XML <![CDATA[" + xmlContent + "]]");
			toNode.addChild(el);
		}
	}
	
	protected String getPlural (String name){
		if (name.endsWith("s")){
			return name + "List";
		}
		return name + "s";
	}

}
